package com.hermeneute.st.base;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Iterator;
import java.util.LinkedList;

public class FileByteArray {
	static public enum Policy {LRU, MRU, RANDOM}
	class Buffer {
		int position = 0;
		ByteBuffer buffer = null;
		Buffer() {
			buffer = ByteBuffer.allocate(bufferSize);
		}
	}
	private int pageSize = 4 * 1024; // We suppose a page is 4 Kb
	private int pbb = 1; // 1 page by buffer
	final public int bufferSize = pbb * pageSize;
	private FileChannel channel = null;
	final public int vms = (1024*1024*2)/ bufferSize * 1024;
	private Buffer[] buffers = null;
	private String fileName = null;
	private String mode = "rw";
	private Policy policy = Policy.LRU;
	int nob = 2;
	LinkedList<Buffer> bufferList = new LinkedList<Buffer>();
	public int faults = 0;
	
	public FileByteArray(String fileName, int nob) throws IOException {
		this.fileName = fileName;
		this.nob = nob;
		init();
	}
	
	public FileByteArray(String fileName, int nob, String mode) throws IOException {
		this.fileName = fileName;
		this.nob = nob;
		this.mode = mode;
		init();
	}
	
	void init() throws IOException {
		File file = new File(fileName);
		//System.out.println(file.getAbsolutePath());
		if(!file.exists()) {
			file.createNewFile();
		}
		RandomAccessFile rfile = new RandomAccessFile(file,mode);
		channel = rfile.getChannel();
		//System.out.println("Size: "+channel.size());
		buffers = new Buffer[vms];
	}
	
	ByteBuffer lru(int position) throws IOException {
		Buffer res = null;
		if (buffers[position] == null) {
			if (bufferList.size() < nob) {
				res = new Buffer();
				channel.read(res.buffer,position*bufferSize);
				res.buffer.flip();
				res.position = position;
				buffers[position] = res;
				bufferList.push(res);
				faults++;
			} else {
				res = bufferList.removeLast();
				if("rw".equals(mode)) {
					channel.write(res.buffer, res.position*bufferSize);
				}
				buffers[res.position] = null;
				res.position = position;
				res.buffer.clear();
				faults++;
				channel.read(res.buffer,res.position*bufferSize);
				res.buffer.flip();
				buffers[position] = res;
				bufferList.push(res);
			}
		} else {
			res = buffers[position];
			res.buffer.flip();
			if (bufferList.peek() != res) {
				bufferList.remove(res);
				bufferList.push(res);
			}
		}
		return res.buffer;
	}
	
	ByteBuffer mru(int position) throws IOException {
		Buffer res = null;
		if (buffers[position] == null) {
			if (bufferList.size() < nob) {
				res = new Buffer();
				channel.read(res.buffer,position*bufferSize);
				res.buffer.flip();
				res.position = position;
				buffers[position] = res;
				bufferList.push(res);
				faults++;
			} else {
				res = bufferList.pop();
				if("rw".equals(mode)) {
					channel.write(res.buffer, res.position*bufferSize);
				}
				buffers[res.position] = null;
				res.position = position;
				res.buffer.clear();
				faults++;
				channel.read(res.buffer,res.position*bufferSize);
				res.buffer.flip();
				buffers[position] = res;
				bufferList.push(res);
			}
		} else {
			res = buffers[position];
			res.buffer.flip();
			if (bufferList.peek() != res) {
				bufferList.remove(res);
				bufferList.push(res);
			}
		}
		return res.buffer;
	}

	ByteBuffer random(int position) throws IOException {
		Buffer res = null;
		if (buffers[position] == null) {
			if (bufferList.size() < nob) {
				res = new Buffer();
				channel.read(res.buffer,position*bufferSize);
				res.buffer.flip();
				res.position = position;
				buffers[position] = res;
				bufferList.push(res);
				faults++;
			} else {
				int random = (int)Math.floor(Math.random()*nob);
				res = bufferList.remove(random);
				if("rw".equals(mode)) {
					channel.write(res.buffer, res.position*bufferSize);
				}
				buffers[res.position] = null;
				res.position = position;
				res.buffer.clear();
				faults++;
				channel.read(res.buffer,res.position*bufferSize);
				res.buffer.flip();
				buffers[position] = res;
				bufferList.push(res);
			}
		} else {
			res = buffers[position];
			res.buffer.flip();
		}
		return res.buffer;
	}
	
	public ByteBuffer getBuffer(int pos) throws IOException {
		int offset = pos % bufferSize;
		int start = pos - offset;
		int position = start/bufferSize;
		ByteBuffer res = null;
		if(policy == Policy.LRU) {
			res = lru(position);
		} else if(policy == Policy.MRU) {
			res = mru(position);
		} else {
			res = random(position);
		}
		return res;
	}
	
	public void flush() throws IOException {
		if("rw".equals(mode)) {
			for(Iterator<Buffer> it = bufferList.iterator(); it.hasNext();) {
				Buffer res = it.next();
				channel.write(res.buffer, res.position);			
			}
		}
	}
	
	public FileChannel getChannel() {
		return channel;
	}
	
	public long size() throws IOException {
		return getChannel().size();
	}
	
	public void setPolicy(Policy p) {
		policy = p;
	}
}
